// <com.woyouqiu.Copyright>
// --------------------------------------------------------------
// <copyright>上海有求网络科技有限公司 2015</copyright>
// <Solution>Vecen.BangChuang</Solution>
// <Project>Vecen.BangChuang.Core</Project>
// <FileName>StringExtensions.cs</FileName>
// <CreateTime>2017-05-03 11:52</CreateTime>
// <Author>何苗</Author>
// <Email>hemiao@woyouqiu.com</Email>
// <log date="2017-05-03 11:52" version="00001">创建</log>
// --------------------------------------------------------------
// </com.woyouqiu.Copyright>

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Castle.DynamicProxy;

namespace Vecen.BangChuang.Helpers {
    public static class StringExtensions {
        /// <summary>
        /// 字符串截断
        /// </summary>
        /// <param name="value"></param>
        /// <param name="length"></param>
        /// <param name="suffix"></param>
        /// <returns></returns>
        public static string Cut(this string value, int length, string suffix = "…") {
            if(string.IsNullOrEmpty(value)) {
                return value;
            }

            if(length < 1) {
                length = 20;
            }

            if(value.Length > length) {
                return value.Substring(0, length) + suffix;
            }

            return value;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string ReplaceNewlineSymbol(this string value) {
            if(string.IsNullOrEmpty(value)) {
                return string.Empty;
            }

            return value.Replace("\r", " ").Replace("\n", " ");
        }

        /// <summary>
        /// 替换字符串中的指定部分
        /// </summary>
        /// <param name="value"></param>
        /// <param name="start"></param>
        /// <param name="length"></param>
        /// <param name="stars"></param>
        /// <returns></returns>
        public static string Mosaics(this string value, int start, int length, string stars = "**") {
            if(string.IsNullOrEmpty(value)) {
                return string.Empty;
            }

            if(start >= value.Length - 1) {
                return value;
            }

            if(start + length > value.Length) {
                length = value.Length - start;
            }

            if(length == 0) {
                return value;
            }

            var sub = value.Substring(start, length);
            return value.Replace(sub, stars);
        }

        /// <summary>
        /// 去除HTML标签
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string RemoveHtmlTags(this string value) {
            if(string.IsNullOrEmpty(value)) {
                return string.Empty;
            }

            return Regex.Replace(value, "<.*?>", string.Empty);
        }

        public static bool IsUrl(this string value) {
            if(string.IsNullOrWhiteSpace(value)) {
                return false;
            }
            return Regex.IsMatch(value, @"^((https|http|ftp|rtsp|mms)?://)"
                                        + @"?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" // ftp的user@
                                        + @"(([0-9]{1,3}\.){3}[0-9]{1,3}" // IP形式的URL- 199.194.52.184
                                        + @"|" // 允许IP和DOMAIN（域名）
                                        + @"([0-9a-z_!~*'()-]+\.)*" // 域名- www.
                                        + @"([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\." // 二级域名
                                        + @"[a-z]{2,6})" // first level domain- .com or .museum
                                        + @"(:[0-9]{1,4})?" // 端口- :80
                                        + @"((/?)|" // a slash isn't required if there is no file name
                                        + @"(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$");
        }

        public static T To<T>(this string value) {
            return (T)To(value, typeof(T));
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <param name="destinationType"></param>
        /// <returns></returns>
        public static object To(object value, Type destinationType) {
            return To(value, destinationType, CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <param name="destinationType"></param>
        /// <param name="culture"></param>
        /// <returns></returns>
        public static object To(object value, Type destinationType, CultureInfo culture) {
            if(value != null) {
                var sourceType = value.GetType();

                TypeConverter destinationConverter = GetCustomTypeConverter(destinationType);
                TypeConverter sourceConverter = GetCustomTypeConverter(sourceType);

                if(destinationConverter != null && destinationConverter.CanConvertFrom(value.GetType())) {
                    return destinationConverter.ConvertFrom(null, culture, value);
                }
                if(sourceConverter != null && sourceConverter.CanConvertTo(destinationType)) {
                    return sourceConverter.ConvertTo(null, culture, value, destinationType);
                }
                if(destinationType.GetTypeInfo().IsEnum && value is int) {
                    return Enum.ToObject(destinationType, (int)value);
                }
                if(!destinationType.IsInstanceOfType(value)) {
                    return Convert.ChangeType(value, destinationType, culture);
                }
            }

            return value;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static TypeConverter GetCustomTypeConverter(Type type) {
            if(type == typeof(List<int>)) {
                return new GenericListTypeConverter<int>();
            }
            if(type == typeof(List<decimal>)) {
                return new GenericListTypeConverter<decimal>();
            }
            if(type == typeof(List<string>)) {
                return new GenericListTypeConverter<string>();
            }

            return TypeDescriptor.GetConverter(type);
        }



        /// <summary>
        /// 
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static string FromNow(this DateTime dateTime)
        {
            const int SECOND = 1;
            const int MINUTE = 60 * SECOND;
            const int HOUR = 60 * MINUTE;
            const int DAY = 24 * HOUR;
            const int MONTH = 30 * DAY;
            TimeSpan ts = DateTime.Now - dateTime;
            double delta = ts.TotalSeconds;
            if(delta < 0) {
                return ToNow(dateTime);
            }
            if (delta < 45 * SECOND)
            {
                return "刚刚";
            }
            if (delta < 2 * MINUTE)
            {
                return "1分钟之前";
            }
            if (delta < 45 * MINUTE)
            {
                return ts.Minutes + "分钟";
            }
            if (delta < 90 * MINUTE)
            {
                return "1小时前";
            }
            if (delta >= 90 * MINUTE && delta < 22 * HOUR)
            {
                return ts.Hours + "小时前";
            }
            if (delta >= 22 * HOUR && delta < 36 * HOUR)
            {
                return "昨天";
            }
            if (delta < 30 * DAY)
            {
                return ts.Days + "天之前";
            }
            if (delta < 12 * MONTH)
            {
                int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
                return months <= 1 ? "一个月之前" : months + "月之前";
            }
            else
            {
                int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
                return years <= 1 ? "一年前" : years + "年前";
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static string ToNow(this DateTime dateTime)
        {
            const int SECOND = -1;
            const int MINUTE = 60 * SECOND;
            const int HOUR = 60 * MINUTE;
            const int DAY = 24 * HOUR;
            const int MONTH = 30 * DAY;
            TimeSpan ts = DateTime.Now - dateTime;
            double delta = ts.TotalSeconds;
            if(delta > 0) {
                return FromNow(dateTime);
            }
            if (delta > 45 * SECOND)
            {
                return "在几秒钟内";
            }
            if (delta > 90 * SECOND)
            {
                return "1分钟内";
            }
            if (delta > 2 * MINUTE)
            {
                return "1分钟内";
            }
            if (delta > 45 * MINUTE)
            {
                return ts.Minutes * -1 + "分钟内";
            }
            if (delta > 90 * MINUTE)
            {
                return "1小时内";
            }
            if (delta <= 90 * MINUTE && delta > 22 * HOUR)
            {
                return ts.Hours * -1 + "小时内";
            }
            if (delta <= 22 * HOUR && delta > 36 * HOUR)
            {
                return "一天内";
            }
            if (delta > 30 * DAY)
            {
                return ts.Days * -1 + "天内";
            }
            if (delta > 12 * MONTH)
            {
                int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
                return months >= -1 ? "一个月内" : months * -1 + "月之内";
            }
            else
            {
                int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
                return years >= -1 ? "一年内" : years * -1 + "年内";
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public sealed class GenericListTypeConverter<T>: TypeConverter {
        private readonly TypeConverter _typeConverter;

        /// <summary>
        /// 
        /// </summary>
        public GenericListTypeConverter() {
            _typeConverter = TypeDescriptor.GetConverter(typeof(T));
            AssertionConcern.NotNull(_typeConverter, typeof(T).FullName + " 的类型转换器不存在。");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private string[] GetStringArray(string input) {
            if(!string.IsNullOrEmpty(input)) {
                return input.Split(',').Select(s => s.Trim()).ToArray();
            } else {
                return new string[0];
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="sourceType"></param>
        /// <returns></returns>
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
            if(sourceType == typeof(string)) {
                return GetStringArray(sourceType.ToString()).Any();
            }

            return base.CanConvertFrom(context, sourceType);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="culture"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
            if(value is string) {
                return GetStringArray((string)value)
                    .Select(s => _typeConverter.ConvertFromInvariantString(s))
                    .Where(item => item != null)
                    .Select(item => (T)item)
                    .ToList();
            }
            return base.ConvertFrom(context, culture, value);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="culture"></param>
        /// <param name="value"></param>
        /// <param name="destinationType"></param>
        /// <returns></returns>
        public override object ConvertTo(ITypeDescriptorContext context,
            CultureInfo culture,
            object value,
            Type destinationType) {
            if(destinationType == typeof(string)) {
                IList<T> list = value as IList<T>;
                if(list == null) {
                    return string.Empty;
                }

                return string.Join(",", list.Select(item => Convert.ToString(item, CultureInfo.InvariantCulture)));
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public class AssertionConcern {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="object1"></param>
        /// <param name="object2"></param>
        /// <param name="message"></param>
        public static void Equals(object object1, object object2, string message) {
            if(!object1.Equals(object2)) {
                throw new InvalidOperationException(message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="object1"></param>
        /// <param name="object2"></param>
        /// <param name="message"></param>
        public static void NotEquals(object object1, object object2, string message) {
            if(object1.Equals(object2)) {
                throw new InvalidOperationException(message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stringValue"></param>
        /// <param name="maximum"></param>
        /// <param name="message"></param>
        public static void Length(string stringValue, int maximum, string message) {
            int length = stringValue.Trim().Length;
            if(length > maximum) {
                throw new InvalidOperationException(message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stringValue"></param>
        /// <param name="minimum"></param>
        /// <param name="maximum"></param>
        /// <param name="message"></param>
        public static void Length(string stringValue, int minimum, int maximum, string message) {
            int length = stringValue.Trim().Length;
            if(length < minimum || length > maximum) {
                throw new InvalidOperationException(message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pattern"></param>
        /// <param name="stringValue"></param>
        /// <param name="message"></param>
        public static void Matches(string pattern, string stringValue, string message) {
            System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(pattern);
            if(!regex.IsMatch(stringValue)) {
                throw new InvalidOperationException(message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stringValue"></param>
        /// <param name="message"></param>
        public static void NotEmpty(string stringValue, string message) {
            if(stringValue == null || stringValue.Trim().Length == 0) {
                throw new InvalidOperationException(message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="object1"></param>
        /// <param name="message"></param>
        public static void NotNull(object object1, string message) {
            if(object1 == null) {
                throw new InvalidOperationException(message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <param name="minimum"></param>
        /// <param name="maximum"></param>
        /// <param name="message"></param>
        public static void Range(double value, double minimum, double maximum, string message) {
            if(value < minimum || value > maximum) {
                throw new InvalidOperationException(message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <param name="minimum"></param>
        /// <param name="maximum"></param>
        /// <param name="message"></param>
        public static void Range(float value, float minimum, float maximum, string message) {
            if(value < minimum || value > maximum) {
                throw new InvalidOperationException(message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <param name="minimum"></param>
        /// <param name="maximum"></param>
        /// <param name="message"></param>
        public static void Range(int value, int minimum, int maximum, string message) {
            if(value < minimum || value > maximum) {
                throw new InvalidOperationException(message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <param name="minimum"></param>
        /// <param name="maximum"></param>
        /// <param name="message"></param>
        public static void Range(long value, long minimum, long maximum, string message) {
            if(value < minimum || value > maximum) {
                throw new InvalidOperationException(message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="boolValue"></param>
        /// <param name="message"></param>
        public static void True(bool boolValue, string message) {
            if(!boolValue) {
                throw new InvalidOperationException(message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="boolValue"></param>
        /// <param name="message"></param>
        public static void False(bool boolValue, string message) {
            if(boolValue) {
                throw new InvalidOperationException(message);
            }
        }
    }
}